/*
 * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id: Number.java,v 1.2.4.1 2005/09/21 09:40:51 pvedula Exp $
 */

package com.sun.org.apache.xalan.internal.xsltc.compiler;

import java.util.ArrayList;

import com.sun.org.apache.bcel.internal.classfile.Field;
import com.sun.org.apache.bcel.internal.generic.ALOAD;
import com.sun.org.apache.bcel.internal.generic.ILOAD;
import com.sun.org.apache.bcel.internal.generic.ASTORE;
import com.sun.org.apache.bcel.internal.generic.BranchHandle;
import com.sun.org.apache.bcel.internal.generic.CHECKCAST;
import com.sun.org.apache.bcel.internal.generic.ConstantPoolGen;
import com.sun.org.apache.bcel.internal.generic.GETFIELD;
import com.sun.org.apache.bcel.internal.generic.GOTO;
import com.sun.org.apache.bcel.internal.generic.IFNONNULL;
import com.sun.org.apache.bcel.internal.generic.INVOKESPECIAL;
import com.sun.org.apache.bcel.internal.generic.INVOKESTATIC;
import com.sun.org.apache.bcel.internal.generic.INVOKEVIRTUAL;
import com.sun.org.apache.bcel.internal.generic.InstructionList;
import com.sun.org.apache.bcel.internal.generic.D2I;
import com.sun.org.apache.bcel.internal.generic.LocalVariableGen;
import com.sun.org.apache.bcel.internal.generic.NEW;
import com.sun.org.apache.bcel.internal.generic.PUSH;
import com.sun.org.apache.bcel.internal.generic.PUTFIELD;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.ClassGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MatchGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.MethodGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.NodeCounterGenerator;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.RealType;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Type;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.TypeCheckError;
import com.sun.org.apache.xalan.internal.xsltc.compiler.util.Util;

/**
 * @author Jacek Ambroziak
 * @author Santiago Pericas-Geertsen
 */
final class Number extends Instruction implements Closure {

  private static final int LEVEL_SINGLE = 0;
  private static final int LEVEL_MULTIPLE = 1;
  private static final int LEVEL_ANY = 2;

  static final private String[] ClassNames = {
      "com.sun.org.apache.xalan.internal.xsltc.dom.SingleNodeCounter",          // LEVEL_SINGLE
      "com.sun.org.apache.xalan.internal.xsltc.dom.MultipleNodeCounter", // LEVEL_MULTIPLE
      "com.sun.org.apache.xalan.internal.xsltc.dom.AnyNodeCounter"      // LEVEL_ANY
  };

  static final private String[] FieldNames = {
      "___single_node_counter",                  // LEVEL_SINGLE
      "___multiple_node_counter",                // LEVEL_MULTIPLE
      "___any_node_counter"                      // LEVEL_ANY
  };

  private Pattern _from = null;
  private Pattern _count = null;
  private Expression _value = null;

  private AttributeValueTemplate _lang = null;
  private AttributeValueTemplate _format = null;
  private AttributeValueTemplate _letterValue = null;
  private AttributeValueTemplate _groupingSeparator = null;
  private AttributeValueTemplate _groupingSize = null;

  private int _level = LEVEL_SINGLE;
  private boolean _formatNeeded = false;

  private String _className = null;
  private ArrayList _closureVars = null;

  // -- Begin Closure interface --------------------

  /**
   * Returns true if this closure is compiled in an inner class (i.e.
   * if this is a real closure).
   */
  public boolean inInnerClass() {
    return (_className != null);
  }

  /**
   * Returns a reference to its parent closure or null if outermost.
   */
  public Closure getParentClosure() {
    return null;
  }

  /**
   * Returns the name of the auxiliary class or null if this predicate
   * is compiled inside the Translet.
   */
  public String getInnerClassName() {
    return _className;
  }

  /**
   * Add new variable to the closure.
   */
  public void addVariable(VariableRefBase variableRef) {
    if (_closureVars == null) {
      _closureVars = new ArrayList();
    }

    // Only one reference per variable
    if (!_closureVars.contains(variableRef)) {
      _closureVars.add(variableRef);
    }
  }

  // -- End Closure interface ----------------------

  public void parseContents(Parser parser) {
    final int count = _attributes.getLength();

    for (int i = 0; i < count; i++) {
      final String name = _attributes.getQName(i);
      final String value = _attributes.getValue(i);

      if (name.equals("value")) {
        _value = parser.parseExpression(this, name, null);
      } else if (name.equals("count")) {
        _count = parser.parsePattern(this, name, null);
      } else if (name.equals("from")) {
        _from = parser.parsePattern(this, name, null);
      } else if (name.equals("level")) {
        if (value.equals("single")) {
          _level = LEVEL_SINGLE;
        } else if (value.equals("multiple")) {
          _level = LEVEL_MULTIPLE;
        } else if (value.equals("any")) {
          _level = LEVEL_ANY;
        }
      } else if (name.equals("format")) {
        _format = new AttributeValueTemplate(value, parser, this);
        _formatNeeded = true;
      } else if (name.equals("lang")) {
        _lang = new AttributeValueTemplate(value, parser, this);
        _formatNeeded = true;
      } else if (name.equals("letter-value")) {
        _letterValue = new AttributeValueTemplate(value, parser, this);
        _formatNeeded = true;
      } else if (name.equals("grouping-separator")) {
        _groupingSeparator = new AttributeValueTemplate(value, parser, this);
        _formatNeeded = true;
      } else if (name.equals("grouping-size")) {
        _groupingSize = new AttributeValueTemplate(value, parser, this);
        _formatNeeded = true;
      }
    }
  }

  public Type typeCheck(SymbolTable stable) throws TypeCheckError {
    if (_value != null) {
      Type tvalue = _value.typeCheck(stable);
      if (tvalue instanceof RealType == false) {
        _value = new CastExpr(_value, Type.Real);
      }
    }
    if (_count != null) {
      _count.typeCheck(stable);
    }
    if (_from != null) {
      _from.typeCheck(stable);
    }
    if (_format != null) {
      _format.typeCheck(stable);
    }
    if (_lang != null) {
      _lang.typeCheck(stable);
    }
    if (_letterValue != null) {
      _letterValue.typeCheck(stable);
    }
    if (_groupingSeparator != null) {
      _groupingSeparator.typeCheck(stable);
    }
    if (_groupingSize != null) {
      _groupingSize.typeCheck(stable);
    }
    return Type.Void;
  }

  /**
   * True if the has specified a value for this instance of number.
   */
  public boolean hasValue() {
    return _value != null;
  }

  /**
   * Returns <tt>true</tt> if this instance of number has neither
   * a from nor a count pattern.
   */
  public boolean isDefault() {
    return _from == null && _count == null;
  }

  private void compileDefault(ClassGenerator classGen,
      MethodGenerator methodGen) {
    int index;
    ConstantPoolGen cpg = classGen.getConstantPool();
    InstructionList il = methodGen.getInstructionList();

    int[] fieldIndexes = getXSLTC().getNumberFieldIndexes();

    if (fieldIndexes[_level] == -1) {
      Field defaultNode = new Field(ACC_PRIVATE,
          cpg.addUtf8(FieldNames[_level]),
          cpg.addUtf8(NODE_COUNTER_SIG),
          null,
          cpg.getConstantPool());

      // Add a new private field to this class
      classGen.addField(defaultNode);

      // Get a reference to the newly added field
      fieldIndexes[_level] = cpg.addFieldref(classGen.getClassName(),
          FieldNames[_level],
          NODE_COUNTER_SIG);
    }

    // Check if field is initialized (runtime)
    il.append(classGen.loadTranslet());
    il.append(new GETFIELD(fieldIndexes[_level]));
    final BranchHandle ifBlock1 = il.append(new IFNONNULL(null));

    // Create an instance of DefaultNodeCounter
    index = cpg.addMethodref(ClassNames[_level],
        "getDefaultNodeCounter",
        "(" + TRANSLET_INTF_SIG
            + DOM_INTF_SIG
            + NODE_ITERATOR_SIG
            + ")" + NODE_COUNTER_SIG);
    il.append(classGen.loadTranslet());
    il.append(methodGen.loadDOM());
    il.append(methodGen.loadIterator());
    il.append(new INVOKESTATIC(index));
    il.append(DUP);

    // Store the node counter in the field
    il.append(classGen.loadTranslet());
    il.append(SWAP);
    il.append(new PUTFIELD(fieldIndexes[_level]));
    final BranchHandle ifBlock2 = il.append(new GOTO(null));

    // Backpatch conditionals
    ifBlock1.setTarget(il.append(classGen.loadTranslet()));
    il.append(new GETFIELD(fieldIndexes[_level]));

    ifBlock2.setTarget(il.append(NOP));
  }

  /**
   * Compiles a constructor for the class <tt>_className</tt> that
   * inherits from {Any,Single,Multiple}NodeCounter. This constructor
   * simply calls the same constructor in the super class.
   */
  private void compileConstructor(ClassGenerator classGen) {
    MethodGenerator cons;
    final InstructionList il = new InstructionList();
    final ConstantPoolGen cpg = classGen.getConstantPool();

    cons = new MethodGenerator(ACC_PUBLIC,
        com.sun.org.apache.bcel.internal.generic.Type.VOID,
        new com.sun.org.apache.bcel.internal.generic.Type[]{
            Util.getJCRefType(TRANSLET_INTF_SIG),
            Util.getJCRefType(DOM_INTF_SIG),
            Util.getJCRefType(NODE_ITERATOR_SIG),
            com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN
        },
        new String[]{
            "dom",
            "translet",
            "iterator",
            "hasFrom"
        },
        "<init>", _className, il, cpg);

    il.append(ALOAD_0);         // this
    il.append(ALOAD_1);         // translet
    il.append(ALOAD_2);         // DOM
    il.append(new ALOAD(3));    // iterator
    il.append(new ILOAD(4));    // hasFrom

    int index = cpg.addMethodref(ClassNames[_level],
        "<init>",
        "(" + TRANSLET_INTF_SIG
            + DOM_INTF_SIG
            + NODE_ITERATOR_SIG
            + "Z)V");
    il.append(new INVOKESPECIAL(index));
    il.append(RETURN);

    classGen.addMethod(cons);
  }

  /**
   * This method compiles code that is common to matchesFrom() and
   * matchesCount() in the auxillary class.
   */
  private void compileLocals(NodeCounterGenerator nodeCounterGen,
      MatchGenerator matchGen,
      InstructionList il) {
    int field;
    LocalVariableGen local;
    ConstantPoolGen cpg = nodeCounterGen.getConstantPool();

    // Get NodeCounter._iterator and store locally
    local = matchGen.addLocalVariable("iterator",
        Util.getJCRefType(NODE_ITERATOR_SIG),
        null, null);
    field = cpg.addFieldref(NODE_COUNTER, "_iterator",
        ITERATOR_FIELD_SIG);
    il.append(ALOAD_0); // 'this' pointer on stack
    il.append(new GETFIELD(field));
    local.setStart(il.append(new ASTORE(local.getIndex())));
    matchGen.setIteratorIndex(local.getIndex());

    // Get NodeCounter._translet and store locally
    local = matchGen.addLocalVariable("translet",
        Util.getJCRefType(TRANSLET_SIG),
        null, null);
    field = cpg.addFieldref(NODE_COUNTER, "_translet",
        "Lcom/sun/org/apache/xalan/internal/xsltc/Translet;");
    il.append(ALOAD_0); // 'this' pointer on stack
    il.append(new GETFIELD(field));
    il.append(new CHECKCAST(cpg.addClass(TRANSLET_CLASS)));
    local.setStart(il.append(new ASTORE(local.getIndex())));
    nodeCounterGen.setTransletIndex(local.getIndex());

    // Get NodeCounter._document and store locally
    local = matchGen.addLocalVariable("document",
        Util.getJCRefType(DOM_INTF_SIG),
        null, null);
    field = cpg.addFieldref(_className, "_document", DOM_INTF_SIG);
    il.append(ALOAD_0); // 'this' pointer on stack
    il.append(new GETFIELD(field));
    // Make sure we have the correct DOM type on the stack!!!
    local.setStart(il.append(new ASTORE(local.getIndex())));
    matchGen.setDomIndex(local.getIndex());
  }

  private void compilePatterns(ClassGenerator classGen,
      MethodGenerator methodGen) {
    int current;
    int field;
    LocalVariableGen local;
    MatchGenerator matchGen;
    NodeCounterGenerator nodeCounterGen;

    _className = getXSLTC().getHelperClassName();
    nodeCounterGen = new NodeCounterGenerator(_className,
        ClassNames[_level],
        toString(),
        ACC_PUBLIC | ACC_SUPER,
        null,
        classGen.getStylesheet());
    InstructionList il = null;
    ConstantPoolGen cpg = nodeCounterGen.getConstantPool();

    // Add a new instance variable for each var in closure
    final int closureLen = (_closureVars == null) ? 0 :
        _closureVars.size();

    for (int i = 0; i < closureLen; i++) {
      VariableBase var =
          ((VariableRefBase) _closureVars.get(i)).getVariable();

      nodeCounterGen.addField(new Field(ACC_PUBLIC,
          cpg.addUtf8(var.getEscapedName()),
          cpg.addUtf8(var.getType().toSignature()),
          null, cpg.getConstantPool()));
    }

    // Add a single constructor to the class
    compileConstructor(nodeCounterGen);

        /*
         * Compile method matchesFrom()
         */
    if (_from != null) {
      il = new InstructionList();
      matchGen =
          new MatchGenerator(ACC_PUBLIC | ACC_FINAL,
              com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN,
              new com.sun.org.apache.bcel.internal.generic.Type[]{
                  com.sun.org.apache.bcel.internal.generic.Type.INT,
              },
              new String[]{
                  "node",
              },
              "matchesFrom", _className, il, cpg);

      compileLocals(nodeCounterGen, matchGen, il);

      // Translate Pattern
      il.append(matchGen.loadContextNode());
      _from.translate(nodeCounterGen, matchGen);
      _from.synthesize(nodeCounterGen, matchGen);
      il.append(IRETURN);

      nodeCounterGen.addMethod(matchGen);
    }

        /*
         * Compile method matchesCount()
         */
    if (_count != null) {
      il = new InstructionList();
      matchGen = new MatchGenerator(ACC_PUBLIC | ACC_FINAL,
          com.sun.org.apache.bcel.internal.generic.Type.BOOLEAN,
          new com.sun.org.apache.bcel.internal.generic.Type[]{
              com.sun.org.apache.bcel.internal.generic.Type.INT,
          },
          new String[]{
              "node",
          },
          "matchesCount", _className, il, cpg);

      compileLocals(nodeCounterGen, matchGen, il);

      // Translate Pattern
      il.append(matchGen.loadContextNode());
      _count.translate(nodeCounterGen, matchGen);
      _count.synthesize(nodeCounterGen, matchGen);

      il.append(IRETURN);

      nodeCounterGen.addMethod(matchGen);
    }

    getXSLTC().dumpClass(nodeCounterGen.getJavaClass());

    // Push an instance of the newly created class
    cpg = classGen.getConstantPool();
    il = methodGen.getInstructionList();

    final int index = cpg.addMethodref(_className, "<init>",
        "(" + TRANSLET_INTF_SIG
            + DOM_INTF_SIG
            + NODE_ITERATOR_SIG
            + "Z)V");
    il.append(new NEW(cpg.addClass(_className)));
    il.append(DUP);
    il.append(classGen.loadTranslet());
    il.append(methodGen.loadDOM());
    il.append(methodGen.loadIterator());
    il.append(_from != null ? ICONST_1 : ICONST_0);
    il.append(new INVOKESPECIAL(index));

    // Initialize closure variables
    for (int i = 0; i < closureLen; i++) {
      final VariableRefBase varRef = (VariableRefBase) _closureVars.get(i);
      final VariableBase var = varRef.getVariable();
      final Type varType = var.getType();

      // Store variable in new closure
      il.append(DUP);
      il.append(var.loadInstruction());
      il.append(new PUTFIELD(
          cpg.addFieldref(_className, var.getEscapedName(),
              varType.toSignature())));
    }
  }

  public void translate(ClassGenerator classGen, MethodGenerator methodGen) {
    int index;
    final ConstantPoolGen cpg = classGen.getConstantPool();
    final InstructionList il = methodGen.getInstructionList();

    // Push "this" for the call to characters()
    il.append(classGen.loadTranslet());

    if (hasValue()) {
      compileDefault(classGen, methodGen);
      _value.translate(classGen, methodGen);

      // Using java.lang.Math.floor(number + 0.5) to return a double value
      il.append(new PUSH(cpg, 0.5));
      il.append(DADD);
      index = cpg.addMethodref(MATH_CLASS, "floor", "(D)D");
      il.append(new INVOKESTATIC(index));

      // Call setValue on the node counter
      index = cpg.addMethodref(NODE_COUNTER,
          "setValue",
          "(D)" + NODE_COUNTER_SIG);
      il.append(new INVOKEVIRTUAL(index));
    } else if (isDefault()) {
      compileDefault(classGen, methodGen);
    } else {
      compilePatterns(classGen, methodGen);
    }

    // Call setStartNode()
    if (!hasValue()) {
      il.append(methodGen.loadContextNode());
      index = cpg.addMethodref(NODE_COUNTER,
          SET_START_NODE,
          "(I)" + NODE_COUNTER_SIG);
      il.append(new INVOKEVIRTUAL(index));
    }

    // Call getCounter() with or without args
    if (_formatNeeded) {
      if (_format != null) {
        _format.translate(classGen, methodGen);
      } else {
        il.append(new PUSH(cpg, "1"));
      }

      if (_lang != null) {
        _lang.translate(classGen, methodGen);
      } else {
        il.append(new PUSH(cpg, "en"));         // TODO ??
      }

      if (_letterValue != null) {
        _letterValue.translate(classGen, methodGen);
      } else {
        il.append(new PUSH(cpg, Constants.EMPTYSTRING));
      }

      if (_groupingSeparator != null) {
        _groupingSeparator.translate(classGen, methodGen);
      } else {
        il.append(new PUSH(cpg, Constants.EMPTYSTRING));
      }

      if (_groupingSize != null) {
        _groupingSize.translate(classGen, methodGen);
      } else {
        il.append(new PUSH(cpg, "0"));
      }

      index = cpg.addMethodref(NODE_COUNTER, "getCounter",
          "(" + STRING_SIG + STRING_SIG
              + STRING_SIG + STRING_SIG
              + STRING_SIG + ")" + STRING_SIG);
      il.append(new INVOKEVIRTUAL(index));
    } else {
      index = cpg.addMethodref(NODE_COUNTER, "setDefaultFormatting",
          "()" + NODE_COUNTER_SIG);
      il.append(new INVOKEVIRTUAL(index));

      index = cpg.addMethodref(NODE_COUNTER, "getCounter",
          "()" + STRING_SIG);
      il.append(new INVOKEVIRTUAL(index));
    }

    // Output the resulting string to the handler
    il.append(methodGen.loadHandler());
    index = cpg.addMethodref(TRANSLET_CLASS,
        CHARACTERSW,
        CHARACTERSW_SIG);
    il.append(new INVOKEVIRTUAL(index));
  }
}
